#ifndef TLVPKG_H
#define TLVPKG_H

#include <QObject>
#include <QDebug>
#include <QMutex>
#include <QMutexLocker>
#include <QVariantMap>
#include "3rdLib/TinyXml2/tinyxml2.h"


// 主要用来解析TLV的格式数据，具体参考<<分离机数据传输方案和需求.docx>>相关文档

#pragma pack(1)
// TLV包头
struct TLVHead {
    // TLV协议的协议标志
    uchar m_tag[2]{0xA9, 0x00};
    // TLV协议的数据类型, 默认为0, 表示内容为字符串
    uchar m_type[2]{0x00};
    // TLV协议的内容长度, 小端字节序
    uchar m_length[4]{0x00};
};
#pragma pack()

// TLV的Value类, 负责解析和封装
struct TLVContent{
public:
    TLVContent() = default;
    TLVContent(const QString &guid, const QString &msgType, const QString &devId,
               const QString &pageMsg, const QString &xmlContent);
    /*
     * 将接收到的数据进行转化成对象
     * content: 输入格式: @#GUID@#MsgType@#DevID@#PageMsg@#Content
     */
    TLVContent(const QByteArray &content);
    /*
     * 将对象转成QByteArrary, 加上了分割符
     */
    QByteArray toByteArray();

public:
    QString m_guid;
    QString m_msgType;
    QString m_devId;
    QString m_pageMsg;
    QString m_xmlContent;
    bool m_parsingResult = true;

private:
    // 分割符
    static const QByteArray m_splitFlag;
};

// 协议包的类型
enum class MsgType
{
    REQ = 0, // 请求包
    ACK = 1, // 回应包
    ERROR = 2, // 其他包
};

// 设备状态请求的包，包含需要发送的信息
struct DeviceStatueReq {
    DeviceStatueReq() = default;
    DeviceStatueReq(const QString &ip,
                    const QString &devID,
                    const int &pos,
                    const int &status,
                    const QString &msg);
    /*
     * 将对象转成QByteArrary,
     * @return: 返回的值包含TLV包所有部分可以直接发送
     */
    QByteArray toByteArray();

    int m_pos;              // 位置, 0 左边分离程序, 1 右边分离程序
    int m_status;           // 设备状态, 0 关机(离线), 1待机, 2工作, 3异常
    QString m_ip;           // 分离机的IP地址
    QString m_devId;        // 分离机的设备号
    QString m_msg;          // 异常信息, 正常情况为空
    QString m_getTime;      // 获取时间

    static const int cmdype = 5; // 功能码
};

struct OpenCloseRecord
{
    QVariantMap m_dataMap = {
        {"CmdType", 6},
        {"OID", ""},// 开关机记录ID
        {"UserID", ""},//操作者
        {"DevID", ""},//设备
        {"OperTime", ""},//操作时间
        {"CloseOpenFlag", 0}//开关标志
    };
};

struct DataResult
{
    QVariantMap m_dataMap = {
        {"CmdType",2},
        {"EID", ""},//EID 唯一标志
        {"DeviceID", ""},//设备编号
        {"UserID", ""},//操作者
        {"ReCheckUserID", ""},//复核者
        {"BagProduceBarCode", ""},//物料码
        {"ProgramName", ""},//程序名称
        {"Humidity", -100.0f},//湿度
        {"Temperature", -100.0f},//温度
        {"MotherBID", ""},//母袋条码
        {"MotherName", ""},//母袋名称
        {"MotherWeight", 0},//母袋重量
        {"TopBID", ""},//顶部条码
        {"TopName", ""},//顶部名称
        {"TopWeight", 0},//顶部重量
        {"SideBID", ""},//侧面条码
        {"SideName", ""},//侧面名称
        {"SideWeight", 0},//侧面重量
        {"BottomBID", ""},//底部条码
        {"BottomName", ""},//底部名称
        {"BottomWeight", 0},//底部重量
        {"SAGMBID", ""},//保养液条码
        {"SAGMName", ""},//保养液名称
        {"SAGMWeight", 0},//保养液重量
        {"VRBegin", ""},//前目视
        {"VRMiddle", ""},//中目测
        {"VREnd", ""},//后目测
        {"VRMiddleOperName", ""},//中目测人
        {"VREndOperName", ""},//后目测人
        {"ESensorValue", 0},//E光感
        {"Statue", 0},//分离状态
        {"SepDate", ""},//分离日期
        {"StartTime", ""},//开始时间
        {"EndTime", ""},//结束时间
        {"FinishTime", ""},//完成时间
        {"SideFlag", 0},//左右标志
        {"Barcode1", ""},
        {"Barcode2", ""},
        {"Barcode3", ""},
        {"Barcode4", ""},
        {"Barcode5", ""},
        {"SendFlag", 0},//发送标志 0未发送 1已发送
        {"BagType", ""},
        {"BeginWeight", 0},
        {"BeginName", ""},
        {"BreakPos", ""},//掰塞位置
        {"BreakStatus", ""},//掰塞状态
        {"ShakeFlag", 0},//摇匀标志
        {"ShakeTimes", 0},//摇匀次数
        {"FagPlasmaValue", 0},//脂肪减值
        {"FagPlasmaResult", 0},//脂肪浆判断结果
        {"RBCWeight", 0},//浓缩红细胞重量
        {"CentrilCode", ""},//离心机条码
        {"BloodBagID", ""},//血袋批号
        {"MotherTareWeight", 0}, // 母袋空袋重量
        {"OtherTareWeight", 0}// 其他空袋重量
    };
};

struct RealResult
{
    QVariantMap m_dataMap = {
        {"CmdType", 3},
        {"EID", ""},//EID 唯一编码
        {"DeviceID", ""},//设备编号
        {"MBID", ""},//母袋条码
        {"StepName", ""},//步骤名称
        {"SourcePos", ""},//原位置
        {"SourceESensor", ""},//源位置光感值
//        {"SourceBID},//原条码
        {"SourceName", ""},//原位置产品名称
        {"SourceWeight", ""},//原位置重量
        {"DestinationPos", ""},//目标位置
//        {"DestinationBID},//目标条码
        {"DestinationName", ""},//目标名称
        {"DestinationWeight", ""},//目标重量
        {"DestinationESensor", ""},//目标位置光感值
        {"PressA_Distance", ""},//挤压板距离
        {"PressA_Foc", ""},//压力
        {"Humidity", ""},//湿度
        {"Temperature", ""},//温度
        {"SepDate", ""},
        {"TimePos", ""},//时间点
        {"Status", ""},//设备状态
        {"SideFlag", 0},//左右标志
        {"SendFlag", 0},
    };
};

struct RealTimesData {
    QVariantMap m_dataMap = {
        {"CmdType", 4},
        {"DeviceID", ""},//设备编号
        {"StepName", ""},//步骤名称
        {"DestValue", ""},// 目标位置
        {"DestName", ""},// 目标位置产品名称
        {"SourceValue", ""},// 源位置值
        {"SourceName", ""},// 源位置产品名称
        {"SourceESensor", ""},// 源位置光感值
        {"DestinationESensor", ""},// 目标光感值
        {"PressForce", ""},// 挤压板压力
        {"PressPos", ""},// 挤压板位置
        {"SourcePlace", ""},// 源位置
        {"DestPlace", ""},// 目标位置
        {"Humidity", ""},//湿度
        {"Temperature", ""},//温度
        {"TimePos", ""},//时间点
    };
};

struct VisualInfo {
    VisualInfo(const int &id, const QString &virsualID,
               const QString &name){
        OrderID = id;
        VirsualID = virsualID;
        VirsualName = name;
    }
    int OrderID;
    QString VirsualID;
    QString VirsualName;
};


/*
 * PC平台每次只会处理一个任务, 因此采用同步处理任务的方式;
 * 接收到的数据都用这个类来转化成对象，同样，从对象转成要发送的数据,
 * 所有的数据封装和解析都在这个类中处理
 *
 * 此类是线程安全的类
 */
class TLVPkg final
{
public:
    TLVPkg() = delete;
    // 接收TLV中的Value值
    TLVPkg(const QByteArray &data);
    ~TLVPkg();
    // -- 配置功能的接口 start
    /*
     * 设置制备程序的根路径, 即program的绝对路径
     * @in parm path: program的路径
     */
    static void setProgramPath(const QString &path);

    /*
     * 设置SQLite的文件数据库的绝对路径
     * @in parm path: DB文件的路径, 包含文件名, 如: /home/BSCS.db3
     */
    static void setDBFilePath(const QString &path);

    // 设置设备ID
    static void setDevId(const QString &devID);

    /*
     * 设置分离机程序的LOG文件路径
     * @in parm path: 文件的路径, 包含文件名, 如: /opt/ponted/apps/app1/var/BSCS.log
     */
    static void setLogPath(const QString &path);

    /* 读取数据库里面的远程PC端的IP地址和Port
     * out parm ip: PC的IP
     * out parm port: PC的端口
     */
    static bool settingInfo(QString &ip, int &port, QString &devID,
                            const QString &dbPath, QString &msg);

    static void setNetAPI(void *pApi);
    // -- 配置功能的接口 end

    // -- 通信功能的接口方法 start
    /*
     * 验证包头
     * @in parm tlvHead: 包头数据, 长度不能小于TLVHead
     * @return: true 验证通过
     */
    static bool verifyPkg(const QByteArray &tlvHead);

    /*
     * 输入包头数据, 返回TLV的Value的长度
     * @in parm tlvHead: 包头数据, 长度不能小于TLVHead
     * @return: TLV的Value的长度
     */
    static long tlvValueLen(const QByteArray &tlvHead);

    /*
     * 解析TLV的Value数据
     * @in parm tlvValue: TLV的Value数据
     * @return: TLVContent为解析后的内容
     */
    static TLVContent parsingTlvValue(const QByteArray &tlvValue);

    /*
     * 根据content的值判断请求还是其他包
     * @in parm content: TLV的内容;
     * @return: 返回MsgType包类型
     */
    static MsgType isReq(const TLVContent &content);

    /*
     * 处理接受到的任务, 根据TLV协议中的Value值中xml内容处理不同的业务, xml中
     * 有个CmdType字段, 这个字段来分派任务，内部调用私有handleXXXX类方法,
     * @in parm content: TLV内容
     * @in parm pClient: 为TcpClientThread的指针, 目的是为了能在处理类中进行
     * 数据的同步循环发送, 不会阻塞信号的接受;
     * @in parm pNetApi: 为NetSyncToPcAPI的指针, 为了将接收到的xml数据发送到
     * 分离机中，去更新分离机程序
     * @return: 处理争取返回true, 主要错误都通过通信协议应答在错误信息字段中了;
     */
    static bool handleTask(const TLVContent &content,
                           void *pClient);


    /*
     * 将map转为xml对象, OpenCloseRecord, DataResult, RealResult
     */
    static QByteArray mapToByteArray(const QVariantMap &map);
    // -- 通信功能的接口方法 end

    // -- 分离机中间件封装协议的接口 start
    // 生成用户信息请求的包
    static QByteArray userInfoReq();
    // 生成目测结论请求的包
    static QByteArray virsualInfoReq();
    // -- 分离机中间件封装协议的接口 end

private:
    // -- 分离机中间件封装协议的接口 start
    // 查询文件夹及文件
    static bool handleReqDirsFiles(const TLVContent &content,
                                   tinyxml2::XMLDocument &doc,
                                   tinyxml2::XMLElement *cmd);
    // 增加文件
    static bool handleAddFile(const TLVContent &content,
                              tinyxml2::XMLDocument &doc,
                              tinyxml2::XMLElement *cmd);
    // 删除文件
    static bool handleDelFile(const TLVContent &content,
                              tinyxml2::XMLDocument &doc,
                              tinyxml2::XMLElement *cmd);
    // 应用文件夹和文件
    static bool handleUseDirFile(const TLVContent &content,
                                 tinyxml2::XMLDocument &doc,
                                 tinyxml2::XMLElement *cmd);
    // 设备状态消息应答处理
    static bool handleDeviceStatusAck(const TLVContent &content,
                                      tinyxml2::XMLDocument &doc,
                                      tinyxml2::XMLElement *cmd);
    // 开关机记录应答处理
    static bool handleOpenCloseRecordAck(const TLVContent &content,
                                         tinyxml2::XMLDocument &doc,
                                         tinyxml2::XMLElement *cmd);
    // 过程数据应答处理
    static bool handleStepDataAck(const TLVContent &content,
                                  tinyxml2::XMLDocument &doc,
                                  tinyxml2::XMLElement *cmd);
    // 实时数据应答处理
    static bool handleRealTimeDataAck(const TLVContent &content,
                                      tinyxml2::XMLDocument &doc,
                                      tinyxml2::XMLElement *cmd);
    // 结果数据应答处理
    static bool handleDataResultAck(const TLVContent &content,
                                    tinyxml2::XMLDocument &doc,
                                    tinyxml2::XMLElement *cmd);

    // 日志数据的上传
    static bool handleReqLogFile(const TLVContent &content,
                                    tinyxml2::XMLDocument &doc,
                                    tinyxml2::XMLElement *cmd);
    // 处理PC发过来的用户信息
    static bool handlePostUserInfo(const TLVContent &content,
                                   tinyxml2::XMLDocument &doc,
                                   tinyxml2::XMLElement *cmd);

    // 处理PC发过来的目测数据
    static bool handlePostVirsualInfo(const TLVContent &content,
                                      tinyxml2::XMLDocument &doc,
                                      tinyxml2::XMLElement *cmd);

    // 读取CmdType数据
    static int cmdTypeFromXml(const TLVContent &content,
                              tinyxml2::XMLDocument &doc,
                              tinyxml2::XMLElement **cmd);

    // 封装通用ACK结果
    static QString ackCommonXml(const int &cmd, const int &result,
                                const QString &msg);

    // 从xml中获取的FilePath中获取文件名称, 路径以"/"分割
    static QString fileNameFromFilePath(const QString &filePath);
    // 从xml中获取的FilePath中获取目录名称, 路径以"/"分割
    static QString folderNameFromFilePath(const QString &filePath);
    // 增加目录
    static bool addDirs(QVector<QString> &dirs);
    // 删除文件
    static bool rmFiles(QVector<QString> &filesPath,
                        QVector<QString> &deletedFile,
                        QString &error);
    // 查询目录下是否是全文件或者全文件夹, isAllDir=true 全文件夹, false 全文件
    static bool isAllDirOrFile(const QString &path,
                               const bool &isAllDir=true);
    // 写入文件
    static bool writeFile(const QString &filePath,
                          const QString &content, QString &error);
    // 读取文件
    static QString readFile(const QString &filePath, bool isAbsolutePath=false);

    // 数据库中增加文件
    static bool addFileToDB(const QString &filePath, QString &error);
    // 数据库中删除指定文件
    static bool delFileFromDB(const QString &filePath, QString &error);
    static QList<QStringList> selectFilesFromDb();

    // 根据xml内容和请求的content来封装TLV包
    static QByteArray pkgFromTLV(const TLVContent &content,
                                 const QString &xml,
                                 const QString pageMsg="1/1",
                                 const bool &isAck=true);

    // 从Program中删除所有文件夹及文件，从数据库中删除所有制备文件信息
    static bool clearFileAndDB(QString &error);

    // 清空用户信息表，然后插入新的值
    static bool replaceUserInfoToDB(const QList<QPair<QString, QString> > &userInfos,
                                    QString &error);
    // 清空目测结论表, 然后插入新的值
    static bool replaceVirsualInfoToDB(const QList<VisualInfo > &virsualInfos,
                                       QString &error);
    // -- 分离机中间件封装协议的接口 end

private:
    static QVector<bool(*)(const TLVContent &, tinyxml2::XMLDocument &,
                           tinyxml2::XMLElement *)> m_handleCmdFuncs;
    static const QString m_databaseName;
    static QString m_filePath;
    static void *m_pClient;
    static void *m_pNetApi;
    static QString m_logPath;
};

#endif // TLVPKG_H
